package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.util.SkyFactory;
import de.lessvoid.nifty.Nifty;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.SwingConstants;
import mygame.controller.Controller;
import mygame.domain.model.Level;
import mygame.utils.Utils;
import mygame.view.listener.KeyActionListener;
import mygame.view.listener.ShootActionListener;
import mygame.view.domain.model.Brick;
import mygame.view.domain.model.MenuOption;
import mygame.view.domain.model.SnowBall;

import static mygame.utils.Utils.TAB;

/**
 * @author Diego Vinter RA: 090428
 *         Joao Correa RA: 081457
 */
public class Main extends SimpleApplication {

    //Definição das constantes
    public static final String LINE_SEPARATOR = System.getProperty("line.separator");
    private static final int SCREEN_WIDTH = 1024;
    private static final int SCREEN_HEIGHT = 768;
    private static final int BRICKS_PER_LAYER = 8;
    private static final int BRICKS_LAYERS = 63;
    private static final float SPHERE_COLLISION_AREA = 0.4f;
    private static final float BULLET_LINEAR_VELOCITY_FACTOR = 50;
    private static final int SCORE_FONT_SIZE = 26;
    private static final String SCORE_TEXT = "BLOCOS REMOVIDOS: %s";
    private static final Vector3f SCORE_POSITION = new Vector3f(650, 100, 0);
    private static final Vector3f INFO_POSITION = new Vector3f(100, 750, 0);
    private static final ColorRGBA INFO_COLOR = ColorRGBA.White;
    private static final int AMMUNITION_FONT_SIZE = 52;
    private static final String AMMUNITION_TEXT = "%d";
    private static final Vector3f AMMUNITION_POSITION = new Vector3f(895, 65, 0);
    private static final float RADIUS = 3f;
    private static final float END_TIME = 5f;
    private static final int SLEEP = 2000;
    
    //Definição das variáveis
    private Material snowMaterial;
    private Material baseMaterial;
    private BulletAppState bulletAppState;
    private SphereCollisionShape bulletCollisionShape;
    private Controller controller;
    private GameScreenController screenController;
    private KeyActionListener keyActionListener;
    private ShootActionListener shootActionListener;
    private Nifty nifty;
    private float score;
    private float time;

    
    private BitmapText scoreText;
    private BitmapText ammunitionText;
    private BitmapText aboutText;
    private BitmapText helpText;
    private BitmapText googleCodeText;
    private BitmapText referencesText;
    private BitmapText returnText;
    private BitmapText rankingText;
    private BitmapText optionsText;
    
    public static void main(String[] args) {
        Main gameMain = new Main();
        gameMain.setShowSettings(false);

        AppSettings appSettings = new AppSettings(true);
        appSettings.setResolution(SCREEN_WIDTH, SCREEN_HEIGHT);
        appSettings.put("Title", "Tear Down Tower");

        gameMain.setDisplayFps(false);
        gameMain.setDisplayStatView(false);
        gameMain.setSettings(appSettings);
        
        showSplash();
        
        gameMain.start();
    }

    @Override
    public void simpleInitApp() {
        controller = new Controller(this);

        screenController = new GameScreenController(this);
        stateManager.attach(screenController);
        
        NiftyJmeDisplay niftyJmeDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort);
        nifty = niftyJmeDisplay.getNifty();
        guiViewPort.addProcessor(niftyJmeDisplay);
        nifty.fromXml("Interface/ScreenLayout.xml", "main", screenController);
        
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        
        initMaterials();
        initBitmapTexts();
        initShadow();
        initSky();
                
        //Inicializa os listeners de teclado e mouse
        shootActionListener = new ShootActionListener(controller, snowMaterial);
        keyActionListener = new KeyActionListener(controller);
        inputManager.addMapping(ShootActionListener.SHOOT_ACTION_NAME, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addMapping(KeyActionListener.RESTART_ACTION_NAME, new KeyTrigger(KeyInput.KEY_R));
        inputManager.addMapping(KeyActionListener.MENU_ACTION_NAME, new KeyTrigger(KeyInput.KEY_M));

        bulletCollisionShape = new SphereCollisionShape(SPHERE_COLLISION_AREA);

        flyCam.setMoveSpeed(0);
        flyCam.setZoomSpeed(0);

        //Exibe o menu principal
        showMenu();
    }
       
    @Override
    public void simpleUpdate(float tpf) {
        updateScore();
        updateAmmunition();

        if (!controller.isEndGame() && (controller.isEmptyAmmunition() || score == 1f)) {
            time += tpf;
            
            if (time > END_TIME) {
                controller.endGame(score);
                resetCam();
                showFinalScore();
                showKeyboardOptions();
            }
        }
    }
    
    private void resetScreen () {
        time = 0;
        
        guiNode.detachAllChildren();
        rootNode.detachAllChildren();
        
        bulletAppState.getPhysicsSpace().destroy();
        bulletAppState.getPhysicsSpace().create();
        
        initTowerBase();
        initTower();
        initSky();
        
        cam.setLocation(new Vector3f(51.1f, 18.3f, 0));
        cam.lookAt(new Vector3f(0, 16, 0), new Vector3f(0, 0, 0));
        cam.setFrustumFar(80);
    }
    
    //Inicia os materiais
    private void initMaterials () {
        //Inicia o material do bloco da torre
        snowMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture textureSnow = assetManager.loadTexture("Textures/snow.jpg");
        snowMaterial.setTexture("ColorMap", textureSnow);
        
        //Inicia o material da base da torre
        baseMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture textureBase = assetManager.loadTexture("Textures/ice.jpg");
        textureBase.setWrap(Texture.WrapMode.Repeat);
        baseMaterial.setTexture("ColorMap", textureBase);
    }
    
    private void initBitmapTexts () {
        StringBuilder sb;
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        
        scoreText = new BitmapText(guiFont, false);
        scoreText.setSize(SCORE_FONT_SIZE);
        scoreText.setColor(ColorRGBA.Yellow);
        scoreText.setLocalTranslation(SCORE_POSITION);
        
        ammunitionText = new BitmapText(guiFont, false);
        ammunitionText.setSize(AMMUNITION_FONT_SIZE);
        ammunitionText.setColor(ColorRGBA.White);
        ammunitionText.setLocalTranslation(AMMUNITION_POSITION);
        
        aboutText = new BitmapText(guiFont, false);
        aboutText.setSize(30);
        aboutText.setColor(INFO_COLOR);
        aboutText.setLocalTranslation(INFO_POSITION);
        sb = new StringBuilder()
                .append("Sobre").append(LINE_SEPARATOR)
                .append(LINE_SEPARATOR)
                .append("Diego de Oliveira Vinter - RA: 090428")
                .append(LINE_SEPARATOR)
                .append("Joao Correa - RA: 081457");
        aboutText.setText(sb.toString());
        
        helpText = new BitmapText(guiFont, false);
        helpText.setSize(30);
        helpText.setColor(INFO_COLOR);
        helpText.setLocalTranslation(INFO_POSITION);
        sb = new StringBuilder()
                .append("Como Jogar?").append(LINE_SEPARATOR)
                .append(LINE_SEPARATOR)
                .append("O objetivo do game Tear Down Tower é destruir a torre de blocos").append(LINE_SEPARATOR)
                .append("de gelo, atirando bolas de neve, deixando a menor quantidade de ").append(LINE_SEPARATOR)
                .append("blocos possível na plataforma onde a torre se encontra.").append(LINE_SEPARATOR)
                .append(LINE_SEPARATOR)                
                .append("Para atirar, basta posicionar a mira e clicar com o botao").append(LINE_SEPARATOR)
                .append("esquerdo do mouse. A quantidade de bolas de neves restantes ").append(LINE_SEPARATOR)
                .append("e o percentual de blocos removidos (score) podem ser visualizados no").append(LINE_SEPARATOR)
                .append("canto inferior direito da tela.").append(LINE_SEPARATOR)
                .append("A  estrategia baseia-se em quando e onde atirar.").append(LINE_SEPARATOR)
                .append("Durante o jogo, pressione 'ESC' para sair, 'R' para reiniciar ou").append(LINE_SEPARATOR)
                .append("'M' para menu");
        helpText.setText(sb.toString());
        
        googleCodeText = new BitmapText(guiFont, false);
        googleCodeText.setSize(30);
        googleCodeText.setColor(INFO_COLOR);
        googleCodeText.setLocalTranslation(INFO_POSITION);
        sb = new StringBuilder()
                .append("Google Code").append(LINE_SEPARATOR)
                .append(LINE_SEPARATOR)
                .append("https://code.google.com/p/tear-down-tower/");
        googleCodeText.setText(sb.toString());
        
        referencesText = new BitmapText(guiFont, false);
        referencesText.setSize(22);
        referencesText.setColor(INFO_COLOR);
        referencesText.setLocalTranslation(new Vector3f(10, 750, 0));
        sb = new StringBuilder()
                .append("Referências")
                .append(LINE_SEPARATOR).append(LINE_SEPARATOR)
                .append("shoot.wav <http://www.eventsounds.com/wav/golfshot.wav>").append(LINE_SEPARATOR).append(LINE_SEPARATOR)
                .append("wind.wav <http://www.shockwave-sound.com/sound-effects/wind-sounds/vent%20wind%203.wav>").append(LINE_SEPARATOR).append(LINE_SEPARATOR)
                .append("snowball.png").append(LINE_SEPARATOR)
                .append("<http://3.bp.blogspot.com/-IfcPhGZ0wAU/UO3Gn8FzkVI/AAAAAAAAEmI/g9mneSTuWjU/s400/Snowball.png").append(LINE_SEPARATOR).append(LINE_SEPARATOR)
                .append("snow.jpg <http://www.cgtextures.com/thumbnails/textures/Ground/Snow/Snow0099_5_thumblarge.jpg>").append(LINE_SEPARATOR).append(LINE_SEPARATOR)
                .append("ice.jpg <http://www.cgtextures.com/thumbnails/textures/Ground/Ice/Ice0044_42_thumblarge.jpg>");
        referencesText.setText(sb.toString());
        
        returnText = new BitmapText(guiFont, false);
        returnText.setSize(30);
        returnText.setColor(INFO_COLOR);
        returnText.setLocalTranslation(new Vector3f(10, 40, 0));
        returnText.setText("Pressione 'M' para retornar ao menu");
        
        rankingText = new BitmapText(guiFont, false);
        rankingText.setSize(30);
        rankingText.setColor(INFO_COLOR);
        rankingText.setLocalTranslation(INFO_POSITION);
        
        optionsText = new BitmapText(guiFont, false);
        optionsText.setSize(30);
        optionsText.setColor(INFO_COLOR);
        optionsText.setLocalTranslation(new Vector3f(10, 40, 0));
        optionsText.setText("Pressione 'ESC' para encerrar, 'R' para reiniciar ou 'M' para menu");
    }
    
    //Inicializa  a base da torre
    private void initTowerBase () {  
        Box baseBox = new Box(10f, 0.1f, 10f);
        Geometry base = new Geometry("base", baseBox);
        base.setMaterial(baseMaterial);
        base.setShadowMode(RenderQueue.ShadowMode.Receive);
        base.setLocalTranslation(0, 0, 0);
        base.addControl(new RigidBodyControl(0));
        this.rootNode.attachChild(base);
        bulletAppState.getPhysicsSpace().add(base);
    }
    
    //Inicializa o céu
    private void initSky () {
        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
        rootNode.attachChild(sky);
    }
    
    //Inicia a torre
    private void initTower () {
        float tempX;
        float tempY = 0;
        float tempZ;
        float angle;
        
        for (int l = 0; l < BRICKS_LAYERS; l++) {
            if (l != 0) {
                tempY += Brick.BRICK_Y_SIZE * 2;
            } else {
                tempY = Brick.BRICK_Y_SIZE;
            }
            angle = 360.0f / BRICKS_PER_LAYER * l / 2f;
            for  (int b = 0; b < BRICKS_PER_LAYER; b++) {                
                tempX = (float)Math.sin(Math.toRadians(angle)) * RADIUS;
                tempZ = (float)Math.cos(Math.toRadians(angle)) * RADIUS;
                
                Vector3f position = new Vector3f(tempX, tempY, tempZ);
                addBrick(position, angle);
                angle += 360.0 / BRICKS_PER_LAYER;
            }
        }
    }
    
    //Adiciona blocos a torre
    private void addBrick (Vector3f position, float angle) {
        Brick brick = new Brick(snowMaterial);
        brick.setLocalTranslation(position);
        brick.rotate(0f, (float)Math.toRadians(angle), 0f);
        brick.addControl(new RigidBodyControl(1.5f));
        brick.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
        brick.getControl(RigidBodyControl.class).setFriction(1.6f);
        this.rootNode.attachChild(brick);
        bulletAppState.getPhysicsSpace().add(brick);
    }
    
        //Inicia a sombra
    public void initShadow () {
        rootNode.setShadowMode(RenderQueue.ShadowMode.Off);
        DirectionalLight sun = new DirectionalLight();
        sun.setColor(ColorRGBA.White);
        sun.setDirection(new Vector3f(1f,-1f,2f).normalizeLocal());
        DirectionalLightShadowRenderer shadowRenderer = new DirectionalLightShadowRenderer(assetManager, 512, 1);
        shadowRenderer.setLight(sun);
        shadowRenderer.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
        viewPort.addProcessor(shadowRenderer);
    }
    
    public void attachBullet (SnowBall bullet) {
        bullet.setLocalTranslation(cam.getLocation());
        RigidBodyControl bulletControl = new ShootHandler(assetManager, bulletCollisionShape, 1);
        bulletControl.setLinearVelocity(cam.getDirection().mult(BULLET_LINEAR_VELOCITY_FACTOR));
        bullet.addControl(bulletControl);
        rootNode.attachChild(bullet); 
        bulletAppState.getPhysicsSpace().add(bulletControl);
    }
    
    //Atualiza a pontuação - percentual de blocos eliminados da base
    private void updateScore () {
        int count = 0;
        for (Spatial spatial : rootNode.getChildren()) {
            if (spatial.getName().equals("brick") && spatial.getLocalTranslation().y < 0) {
                count++;
            }
        }
        score = (float)count / (float)(BRICKS_LAYERS * BRICKS_PER_LAYER);
        scoreText.setText(String.format(SCORE_TEXT, Utils.floatToPercent(score)));
        guiNode.attachChild(scoreText);
    }
    
    //Atualiza quantidade de munição (bolas de neve) disponíveis
    private void updateAmmunition () {
        ammunitionText.setText(String.format(AMMUNITION_TEXT, controller.getAmmunition()));
        guiNode.attachChild(ammunitionText);
    }
    
    //Habilita os controles do jogo: atirar, reiniciar, etc.
    public void enableControls () {
        inputManager.addListener(shootActionListener, ShootActionListener.SHOOT_ACTION_NAME);
        inputManager.addListener(keyActionListener, KeyActionListener.RESTART_ACTION_NAME, KeyActionListener.MENU_ACTION_NAME);
        flyCam.setDragToRotate(false);
        inputManager.setCursorVisible(false);
    }
    
    //Desabilita os controles do jogo
    public void disableControls () {
        inputManager.removeListener(shootActionListener);
        inputManager.removeListener(keyActionListener);
        flyCam.setDragToRotate(true);
        inputManager.setCursorVisible(true);
    }
    
    //Exibe informação na tela
    public void showInfo(MenuOption option) {
        switch (option) {
            case ABOUT:
                guiNode.attachChild(aboutText);
                guiNode.attachChild(returnText);
                break;
            case GOOGLE_CODE:
                guiNode.attachChild(googleCodeText);
                guiNode.attachChild(returnText);
                break;
            case REFERENCES:
                guiNode.attachChild(referencesText);
                guiNode.attachChild(returnText);
                break;
            case HELP:
                guiNode.attachChild(helpText);
                guiNode.attachChild(returnText);
                break;
            case RANKING:
                StringBuilder sb = new StringBuilder();
                sb.append("Ranking - Top 10").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
                sb.append("LEVEL").append(TAB).append("NOME").append(TAB).append("SCORE").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
                sb.append(controller.getRanking());
                rankingText.setText(sb.toString());
                guiNode.attachChild(rankingText);
                guiNode.attachChild(returnText);
                break;
        }

        inputManager.addListener(keyActionListener, KeyActionListener.MENU_ACTION_NAME);
    }
    
    public void showMenu () {
        guiNode.detachAllChildren();
        screenController.showMenu();
    }
    
    public void setAudioEnable(boolean enable) {
        if (enable) { 
            controller.enableAudio();
        } else {
            controller.disableAudio();
        }
    }
    
    public boolean isAudioEnable () {
        return controller.isAudioEnable();
    }
    
    public Level getLevel  () {
        return controller.getLevel();
    }
    
    public void setLevel (Level level) {
        controller.setLevel(level);
    }
    
    public void startGame () {
        enableControls();
        resetScreen();
        controller.start();
    }
    
    public void resetCam () {
        cam.setLocation(new Vector3f(0, 0, 10));
        cam.lookAt(new Vector3f(1, 2, 50), new Vector3f(0, 0, 0));
        cam.setFrustumFar(1000);   
    }

    public void showFinalScore () {
        ColorRGBA color = ColorRGBA.Red;
        
        if (score == 1f) {
            color = ColorRGBA.LightGray;
        } else if (score > 0.9f) {
            color = ColorRGBA.Cyan;
        } else if (score > 0.78f) {
            color = ColorRGBA.Cyan;
        } else if (score > 0.65f) {
            color = ColorRGBA.Cyan;
        } else if (score > 0.5f) {
            color = ColorRGBA.Orange;
        }
        
        BitmapText finalScoreText = new BitmapText(guiFont, false);
        finalScoreText.setSize(60);
        finalScoreText.setColor(color);
        finalScoreText.setLocalTranslation(new Vector3f(300, 450, 0));
        StringBuilder sb = new StringBuilder()
                .append("  FIM DE JOGO")
                .append(LINE_SEPARATOR)
                .append("SCORE: ")
                .append(Utils.floatToPercent(score));
        finalScoreText.setText(sb.toString());
        guiNode.attachChild(finalScoreText);
    }

    public void showKeyboardOptions () {
        guiNode.attachChild(optionsText);
    }
    
    public void setPlayerName (String playerName) {
        controller.setPlayerName(playerName);
    }
    
    public String getPlayerName () {
        return controller.getPlayerName();
    }
    
    public static void showSplash () {
            JWindow window = new JWindow();
            Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
            
            int x = ((int)(dim.getWidth()/2)) - 192;
            int y = ((int)(dim.getHeight()/2)) - 153;
            JLabel label = new JLabel("", new ImageIcon(Utils.getImage("splash1.jpg")), SwingConstants.CENTER);

            window.getContentPane().add(label);
            window.setBounds(x, y, 395, 306);
            window.setVisible(true);
            try {
                Thread.sleep(SLEEP);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            window.remove(label);

            label = new JLabel("", new ImageIcon(Utils.getImage("splash2.jpg")), SwingConstants.CENTER);

            window.getContentPane().add(label);
            window.setVisible(true);
            try {
                Thread.sleep(SLEEP);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            window.remove(label);

            label = new JLabel("", new ImageIcon(Utils.getImage("splash3.jpg")), SwingConstants.CENTER);

            window.getContentPane().add(label);
            window.setVisible(true);
            try {
                Thread.sleep(SLEEP);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            window.remove(label);
            window.setVisible(false);
            window.dispose();
    }
}